﻿Imports System
Imports System.Collections.Generic
Imports System.Diagnostics
Imports System.Globalization
Imports System.Workflow.Runtime

Namespace System.Workflow.ComponentModel
    <Serializable> _
    Friend NotInheritable Class ActivityExecutorDelegateInfo([Of] T As EventArgs)
        ' Methods
        Public Sub New(ByVal delegateValue As EventHandler([Of] T), ByVal contextActivity As Activity)
            Me.New(False, delegateValue, contextActivity)
        End Sub

        Public Sub New(ByVal eventListener As IActivityEventListener([Of] T), ByVal contextActivity As Activity)
            Me.New(False, eventListener, contextActivity)
        End Sub

        Public Sub New(ByVal delegateValue As EventHandler([Of] T), ByVal contextActivity As Activity, ByVal wantInTransact As Boolean)
            Me.New(delegateValue, contextActivity)
            Me.wantInTransact = wantInTransact
        End Sub

        Public Sub New(ByVal eventListener As IActivityEventListener([Of] T), ByVal contextActivity As Activity, ByVal wantInTransact As Boolean)
            Me.New(eventListener, contextActivity)
            Me.wantInTransact = wantInTransact
        End Sub

        Friend Sub New(ByVal useCurrentContext As Boolean, ByVal delegateValue As EventHandler([Of] T), ByVal contextActivity As Activity)
            Me.contextId = -1
            Me.delegateValue = delegateValue
            Dim target As Activity = [TryCast](delegateValue.Target,Activity)
            If (Not contextActivity.WorkflowCoreRuntime Is Nothing) Then
                If useCurrentContext Then
                    Me.contextId = contextActivity.WorkflowCoreRuntime.CurrentActivity.ContextActivity.ContextId
                Else
                    Me.contextId = contextActivity.ContextId
                End If
                Me.activityQualifiedName = If(target <>  Nothing , target, contextActivity.WorkflowCoreRuntime.CurrentActivity).QualifiedName
            Else
                Me.contextId = 1
                Me.activityQualifiedName = If(target <>  Nothing , target, contextActivity.RootActivity).QualifiedName
            End If
        End Sub

        Friend Sub New(ByVal useCurrentContext As Boolean, ByVal eventListener As IActivityEventListener([Of] T), ByVal contextActivity As Activity)
            Me.contextId = -1
            Me.eventListener = eventListener
            Dim activity As Activity = [TryCast](eventListener,Activity)
            If (Not contextActivity.WorkflowCoreRuntime Is Nothing) Then
                If useCurrentContext Then
                    Me.contextId = contextActivity.WorkflowCoreRuntime.CurrentActivity.ContextActivity.ContextId
                Else
                    Me.contextId = contextActivity.ContextId
                End If
                Me.activityQualifiedName = If(activity <>  Nothing , activity, contextActivity.WorkflowCoreRuntime.CurrentActivity).QualifiedName
            Else
                Me.contextId = 1
                Me.activityQualifiedName = If(activity <>  Nothing , activity, contextActivity.RootActivity).QualifiedName
            End If
        End Sub

        Public Overrides Function Equals(ByVal obj As Object) As Boolean
            Dim info As ActivityExecutorDelegateInfo([Of] T) = [TryCast](obj,ActivityExecutorDelegateInfo([Of] T))
            If (info Is Nothing) Then
                Return False
            End If
            Return ((((((info.delegateValue Is Nothing) AndAlso (Me.delegateValue Is Nothing)) OrElse ((Not info.delegateValue Is Nothing) AndAlso info.delegateValue.Equals(Me.delegateValue))) AndAlso (((info.eventListener Is Nothing) AndAlso (Me.eventListener Is Nothing)) OrElse ((Not info.eventListener Is Nothing) AndAlso info.eventListener.Equals(Me.eventListener)))) AndAlso ((info.activityQualifiedName = Me.activityQualifiedName) AndAlso (info.contextId = Me.contextId))) AndAlso (info.wantInTransact = Me.wantInTransact))
        End Function

        Private Function FindExecutorForActivityDown(ByVal contextActivity As Activity, ByVal activityQualifiedName As String) As Activity
            Dim queue As New Queue([Of] Activity)
            queue.Enqueue(contextActivity)
            Do While (queue.Count > 0)
                Dim activity As Activity = queue.Dequeue
                Dim activityByName As Activity = activity.GetActivityByName(activityQualifiedName, True)
                If ((Not activityByName Is Nothing) AndAlso (activityByName.ExecutionStatus <> ActivityExecutionStatus.Initialized)) Then
                    Return activity
                End If
                Dim list As IList([Of] Activity) = DirectCast(activity.GetValue(Activity.ActiveExecutionContextsProperty), IList([Of] Activity))
                If (Not list Is Nothing) Then
                    Dim activity3 As Activity
                    For Each activity3 In list
                        queue.Enqueue(activity3)
                    Next
                End If
            Loop
            Return Nothing
        End Function

        Private Function FindExecutorForActivityUp(ByVal contextActivity As Activity, ByVal activityQualifiedName As String) As Activity
            Do While (Not contextActivity Is Nothing)
                Dim activityByName As Activity = contextActivity.GetActivityByName(activityQualifiedName, True)
                If ((Not activityByName Is Nothing) AndAlso (activityByName.ExecutionStatus <> ActivityExecutionStatus.Initialized)) Then
                    Return contextActivity
                End If
                contextActivity = contextActivity.ParentContextActivity
            Loop
            Return contextActivity
        End Function

        Public Overrides Function GetHashCode() As Integer
            If (Me.delegateValue Is Nothing) Then
                Return (Me.eventListener.GetHashCode Xor Me.activityQualifiedName.GetHashCode)
            End If
            Return Me.delegateValue.GetHashCode
        End Function

        Public Sub InvokeDelegate(ByVal currentContextActivity As Activity, ByVal e As T, ByVal transacted As Boolean)
            Dim targetContextActivity As Activity = Me.FindExecutorForActivityUp(currentContextActivity, Me.activityQualifiedName)
            If (targetContextActivity Is Nothing) Then
                targetContextActivity = Me.FindExecutorForActivityDown(currentContextActivity, Me.activityQualifiedName)
            End If
            If (Not targetContextActivity Is Nothing) Then
                Me.InvokeDelegate(currentContextActivity, targetContextActivity, e, False, transacted)
            End If
        End Sub

        Friend Sub InvokeDelegate(ByVal currentContextActivity As Activity, ByVal e As T, ByVal sync As Boolean, ByVal transacted As Boolean)
            Dim contextActivityForId As Activity = currentContextActivity.WorkflowCoreRuntime.GetContextActivityForId(Me.contextId)
            If (contextActivityForId Is Nothing) Then
                contextActivityForId = Me.FindExecutorForActivityUp(currentContextActivity, Me.activityQualifiedName)
                If (contextActivityForId Is Nothing) Then
                    contextActivityForId = Me.FindExecutorForActivityDown(currentContextActivity, Me.activityQualifiedName)
                End If
            End If
            If (Not contextActivityForId Is Nothing) Then
                Me.InvokeDelegate(currentContextActivity, contextActivityForId, e, sync, transacted)
            End If
        End Sub

        Private Sub InvokeDelegate(ByVal currentContextActivity As Activity, ByVal targetContextActivity As Activity, ByVal e As T, ByVal sync As Boolean, ByVal transacted As Boolean)
            Dim item As ActivityExecutorDelegateOperation([Of] T) = Nothing
            If (Not Me.delegateValue Is Nothing) Then
                item = New ActivityExecutorDelegateOperation([Of] T)(Me.activityQualifiedName, Me.delegateValue, e, Me.ContextId)
            Else
                item = New ActivityExecutorDelegateOperation([Of] T)(Me.activityQualifiedName, Me.eventListener, e, Me.ContextId)
            End If
            Dim flag As Boolean = Me.MayInvokeDelegateNow(currentContextActivity)
            If (flag AndAlso sync) Then
                Dim activity As Activity = targetContextActivity.GetActivityByName(Me.activityQualifiedName)
                [Using] currentContextActivity.WorkflowCoreRuntime.SetCurrentActivity(activity)
                    item.SynchronousInvoke = True
                    item.Run(currentContextActivity.WorkflowCoreRuntime)
                    Return
                End [Using]
            End If
            Dim activityByName As Activity = targetContextActivity.GetActivityByName(Me.activityQualifiedName)
            currentContextActivity.WorkflowCoreRuntime.ScheduleItem(item, ActivityExecutionContext.IsInAtomicTransaction(activityByName), transacted, Not flag)
        End Sub

        Private Function MayInvokeDelegateNow(ByVal currentContextActivity As Activity) As Boolean
            If ((Me.activityQualifiedName Is Nothing) OrElse Me.wantInTransact) Then
                Return True
            End If
            If Not ActivityExecutionContext.IsInAtomicTransaction(currentContextActivity.WorkflowCoreRuntime.CurrentActivity) Then
                Return True
            End If
            Dim contextActivityForId As Activity = currentContextActivity.WorkflowCoreRuntime.GetContextActivityForId(Me.contextId)
            If (contextActivityForId Is Nothing) Then
                Return False
            End If
            Dim activityByName As Activity = contextActivityForId.GetActivityByName(Me.activityQualifiedName, True)
            If (activityByName Is Nothing) Then
                Return False
            End If
            Return ((ActivityExecutionContext.IsInAtomicTransaction(activityByName) AndAlso ActivityExecutionContext.IsInAtomicTransaction(currentContextActivity.WorkflowCoreRuntime.CurrentActivity)) OrElse activityByName.MetaEquals(currentContextActivity))
        End Function


        ' Properties
        Public ReadOnly Property ActivityQualifiedName As String
            Get
                Return Me.activityQualifiedName
            End Get
        End Property

        Public ReadOnly Property ContextId As Integer
            Get
                Return Me.contextId
            End Get
        End Property

        Public ReadOnly Property EventListener As IActivityEventListener([Of] T)
            Get
                Return Me.eventListener
            End Get
        End Property

        Public ReadOnly Property HandlerDelegate As EventHandler([Of] T)
            Get
                Return Me.delegateValue
            End Get
        End Property

        Public Property SubscribedActivityQualifiedName As String
            Get
                Return Me.subscribedActivityQualifiedName
            End Get
            Set(ByVal value As String)
                Me.subscribedActivityQualifiedName = value
            End Set
        End Property


        ' Fields
        Private activityQualifiedName As String
        Private contextId As Integer
        Private delegateValue As EventHandler([Of] T)
        Private eventListener As IActivityEventListener([Of] T)
        Private subscribedActivityQualifiedName As String
        Private wantInTransact As Boolean

        ' Nested Types
        <Serializable> _
        Private NotInheritable Class ActivityExecutorDelegateOperation
            Inherits SchedulableItem
            ' Methods
            Public Sub New(ByVal activityQualifiedName As String, ByVal delegateValue As EventHandler([Of] T), ByVal e As T, ByVal contextId As Integer)
                MyBase.New(contextId, activityQualifiedName)
                Me.args = CType(Nothing, T)
                Me.activityQualifiedName = activityQualifiedName
                Me.delegateValue = delegateValue
                Me.args = e
            End Sub

            Public Sub New(ByVal activityQualifiedName As String, ByVal eventListener As IActivityEventListener([Of] T), ByVal e As T, ByVal contextId As Integer)
                MyBase.New(contextId, activityQualifiedName)
                Me.args = CType(Nothing, T)
                Me.activityQualifiedName = activityQualifiedName
                Me.eventListener = eventListener
                Me.args = e
            End Sub

            Public Overrides Function Run(ByVal workflowCoreRuntime As IWorkflowCoreRuntime) As Boolean
                Dim contextActivityForId As Activity = workflowCoreRuntime.GetContextActivityForId(MyBase.ContextId)
                Dim args As ActivityExecutionStatusChangedEventArgs = [TryCast](Me.args,ActivityExecutionStatusChangedEventArgs)
                If (Not args Is Nothing) Then
                    args.BaseExecutor = workflowCoreRuntime
                    If (args.Activity Is Nothing) Then
                        args.BaseExecutor = Nothing
                        Return False
                    End If
                End If
                Dim activityByName As Activity = contextActivityForId.GetActivityByName(Me.activityQualifiedName)
                If (((activityByName Is Nothing) OrElse (((activityByName.ExecutionStatus = ActivityExecutionStatus.Closed) OrElse (activityByName.ExecutionStatus = ActivityExecutionStatus.Initialized)) AndAlso Not Me.synchronousInvoke)) OrElse (activityByName.HasPrimaryClosed AndAlso Not TypeOf Me.eventListener Is ActivityExecutionFilter)) Then
                    Return False
                End If
                Try 
                    [Using] workflowCoreRuntime.SetCurrentActivity(activityByName)
                        [Using] context As ActivityExecutionContext = New ActivityExecutionContext(activityByName)
                            If (Not Me.delegateValue Is Nothing) Then
                                Me.delegateValue.Invoke(context, Me.args)
                            Else
                                Me.eventListener.OnEvent(context, Me.args)
                            End If
                        End [Using]
                    End [Using]
                Catch exception As Exception
                    If (Not activityByName Is Nothing) Then
                        WorkflowTrace.Runtime.TraceEvent(TraceEventType.Error, 1, "Subscription handler of Activity {0} threw {1}", New Object() { activityByName.QualifiedName, exception.ToString })
                    Else
                        WorkflowTrace.Runtime.TraceEvent(TraceEventType.Error, 1, "Subscription handler threw {0}", New Object() { exception.ToString })
                    End If
                    Throw
                Finally
                    If (Not args Is Nothing) Then
                        args.BaseExecutor = Nothing
                    End If
                End Try
                Return True
            End Function

            Public Overrides Function ToString() As String
                Return String.Concat(New String() { "SubscriptionEvent((", MyBase.ContextId.ToString(CultureInfo.CurrentCulture), ")", Me.activityQualifiedName, ", ", Me.args.ToString, ")" })
            End Function


            ' Properties
            Friend Property SynchronousInvoke As Boolean
                Get
                    Return Me.synchronousInvoke
                End Get
                Set(ByVal value As Boolean)
                    Me.synchronousInvoke = value
                End Set
            End Property


            ' Fields
            Private activityQualifiedName As String
            Private args As T
            Private delegateValue As EventHandler([Of] T)
            Private eventListener As IActivityEventListener([Of] T)
            <NonSerialized> _
            Private synchronousInvoke As Boolean
        End Class
    End Class
End Namespace

